/*
 * Simulator.java
 * RVO2 Library Java
 *
 * Copyright 2008 University of North Carolina at Chapel Hill
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Please send all bug reports to <geom@cs.unc.edu>.
 *
 * The authors may be contacted via:
 *
 * Jur van den Berg, Stephen J. Guy, Jamie Snape, Ming C. Lin, Dinesh Manocha
 * Dept. of Computer Science
 * 201 S. Columbia St.
 * Frederick P. Brooks, Jr. Computer Science Bldg.
 * Chapel Hill, N.C. 27599-3175
 * United States of America
 *
 * <http://gamma.cs.unc.edu/RVO2/>
 */

package RVO;

import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * Defines the simulation.
 */
public class Simulator {
    /**
     * Defines a worker.
     */
    private static class Worker implements Runnable {
        private CountDownLatch doneEvent_;
        private int end_;
        private int start_;
        private boolean isStep_;
        private Simulator simulator;

        /**
         * Constructs and initializes a worker.
         *
         * @param start Start.
         * @param end End.
         * @param doneEvent Done event.
         */
        Worker(Simulator simulator, int start, int end, CountDownLatch doneEvent) {
            start_ = start;
            end_ = end;
            doneEvent_ = doneEvent;
            this.simulator = simulator;
        }

        void config(int start, int end) {
            start_ = start;
            end_ = end;
        }

        void setStep(boolean isStep) {
            isStep_ = isStep;
        }

        /**
         * Performs a simulation step or update.
         */
        @Override
        public void run() {
            /*
            start_ = 0;
            end_ = Simulator.Instance.agents_.size();
             */
            if (isStep_) {
                for (int index = start_; index < end_; ++index) {
                    simulator.agents_.get(index).computeNeighbors();
                    simulator.agents_.get(index).computeNewVelocity();
                }
            } else {
                for (int index = start_; index < end_; ++index) {
                    simulator.agents_.get(index).update();
                }
            }
            doneEvent_.countDown();
        }
    }

    Map<Integer, Integer> agentNo2indexDict_;
    Map<Integer, Integer> index2agentNoDict_;
    public List<Agent> agents_;
    List<Obstacle> obstacles_;
    KdTree kdTree_;
    float timeStep_;

    private Agent defaultAgent_;
    private CountDownLatch[] doneEvents_;
    private Worker[] workers_;
    private int numWorkers_;
    private int workerAgentCount_;
    private float globalTime_;
    private ExecutorService executor_;

    public void delAgent(int agentNo) {
        agents_.get(agentNo2indexDict_.get(agentNo)).needDelete_ = true;
    }

    void updateDeleteAgent() {
        boolean isDelete = false;
        for (int i = agents_.size() - 1; i >= 0; i--) {
            if (agents_.get(i).needDelete_) {
                agents_.remove(i);
                isDelete = true;
            }
        }
        if (isDelete)
            onDelAgent();
    }

    int s_totalID = 0;

    /**
     * Adds a new agent with default properties to the simulation.
     *
     * @param position The two-dimensional starting position of this agent.
     * @return The number of the agent, or -1 when the agent defaults have not been set.
     */
    public int addAgent(Vector2 position) {
        if (defaultAgent_ == null) {
            return -1;
        }

        Agent agent = new Agent();
        agent.id_ = s_totalID;
        s_totalID++;
        agent.maxNeighbors_ = defaultAgent_.maxNeighbors_;
        agent.maxSpeed_ = defaultAgent_.maxSpeed_;
        agent.neighborDist_ = defaultAgent_.neighborDist_;
        agent.position_ = position;
        agent.radius_ = defaultAgent_.radius_;
        agent.timeHorizon_ = defaultAgent_.timeHorizon_;
        agent.timeHorizonObst_ = defaultAgent_.timeHorizonObst_;
        agent.velocity_ = defaultAgent_.velocity_;
        agent.setSimulator(this);
        agents_.add(agent);
        onAddAgent();
        return agent.id_;
    }

    void onDelAgent() {
        agentNo2indexDict_.clear();
        index2agentNoDict_.clear();

        for (int i = 0; i < agents_.size(); i++) {
            int agentNo = agents_.get(i).id_;
            agentNo2indexDict_.put(agentNo, i);
            index2agentNoDict_.put(i, agentNo);
        }
    }

    void onAddAgent() {
        if (agents_.size() == 0)
            return;

        int index = agents_.size() - 1;
        int agentNo = agents_.get(index).id_;
        agentNo2indexDict_.put(agentNo, index);
        index2agentNoDict_.put(index, agentNo);
    }

    /**
     * Adds a new agent to the simulation.
     *
     * @param position The two-dimensional starting position of this agent.
     * @param neighborDist The maximum distance (center point to center point) to other agents this agent takes into account in the navigation. The larger this number, the longer the running time of the simulation. If the number is too low, the simulation will not be safe. Must be non-negative.
     * @param maxNeighbors The maximum number of other agents this agent takes into account in the navigation. The larger this number, the longer the running time of the simulation. If the number is too low, the simulation will not be safe.
     * @param timeHorizon The minimal amount of time for which this agent's velocities that are computed by the simulation are safe with respect to other agents. The larger this number, the sooner this agent will respond to the presence of other agents, but the less freedom this agent has in choosing its velocities. Must be positive.
     * @param timeHorizonObst The minimal amount of time for which this agent's velocities that are computed by the simulation are safe with respect to obstacles. The larger this number, the sooner this agent will respond to the presence of obstacles, but the less freedom this agent has in choosing its velocities. Must be positive.
     * @param radius The radius of this agent. Must be non-negative.
     * @param maxSpeed The maximum speed of this agent. Must be non-negative.
     * @param velocity The initial two-dimensional linear velocity of this agent.
     * @return The number of the agent.
     */
    public int addAgent(Vector2 position, float neighborDist, int maxNeighbors, float timeHorizon, float timeHorizonObst, float radius, float maxSpeed, Vector2 velocity) {
        Agent agent = new Agent();
        agent.id_ = s_totalID;
        s_totalID++;
        agent.maxNeighbors_ = maxNeighbors;
        agent.maxSpeed_ = maxSpeed;
        agent.neighborDist_ = neighborDist;
        agent.position_ = position;
        agent.radius_ = radius;
        agent.timeHorizon_ = timeHorizon;
        agent.timeHorizonObst_ = timeHorizonObst;
        agent.velocity_ = velocity;
        agent.setSimulator(this);
        agents_.add(agent);
        onAddAgent();
        return agent.id_;
    }

    /**
     * Adds a new obstacle to the simulation.
     *
     * @param vertices List of the vertices of the polygonal obstacle in counterclockwise order.
     * @return The number of the first vertex of the obstacle, or -1 when the number of vertices is less than two.
     * @remarks To add a "negative" obstacle, e.g. a bounding polygon around the environment, the vertices should be listed in clockwise order.
     */
    public int addObstacle(List<Vector2> vertices) {
        if (vertices.size() < 2) {
            return -1;
        }

        int obstacleNo = obstacles_.size();

        for (int i = 0; i < vertices.size(); ++i) {
            Obstacle obstacle = new Obstacle();
            obstacle.point_ = vertices.get(i);

            if (i != 0) {
                obstacle.previous_ = obstacles_.get(obstacles_.size() - 1);
                obstacle.previous_.next_ = obstacle;
            }

            if (i == vertices.size() - 1) {
                obstacle.next_ = obstacles_.get(obstacleNo);
                obstacle.next_.previous_ = obstacle;
            }

            obstacle.direction_ = RVOMath.normalize(Vector2.subtract(vertices.get((i == vertices.size() - 1 ? 0 : i + 1)), vertices.get(i)));

            if (vertices.size() == 2) {
                obstacle.convex_ = true;
            } else {
                obstacle.convex_ = (RVOMath.leftOf(vertices.get((i == 0 ? vertices.size() - 1 : i - 1)), vertices.get(i), vertices.get((i == vertices.size() - 1 ? 0 : i + 1))) >= 0.0f);
            }

            obstacle.id_ = obstacles_.size();
            obstacles_.add(obstacle);
        }

        return obstacleNo;
    }

    /**
     * Clears the simulation.
     */
    public void Clear() {
        agents_ = new ArrayList<Agent>();
        agentNo2indexDict_ = new HashMap<Integer, Integer>();
        index2agentNoDict_ = new HashMap<Integer, Integer>();
        defaultAgent_ = null;
        kdTree_ = new KdTree();
        kdTree_.setSimulator(this);
        obstacles_ = new ArrayList<Obstacle>();
        globalTime_ = 0.0f;
        timeStep_ = 0.1f;

        SetNumWorkers(3);
    }

    /**
     * Performs a simulation step and updates the two-dimensional position and two-dimensional velocity of each agent.
     *
     * @return The global time after the simulation step.
     */
    public float doStep() {
        updateDeleteAgent();

        if (workers_ == null) {
            workers_ = new Worker[numWorkers_];
            doneEvents_ = new CountDownLatch[workers_.length];
            workerAgentCount_ = getNumAgents();
            executor_ = Executors.newFixedThreadPool(numWorkers_);

            for (int block = 0; block < workers_.length; ++block) {
                doneEvents_[block] = new CountDownLatch(1);
                workers_[block] = new Worker(this, block * getNumAgents() / workers_.length, (block + 1) * getNumAgents() / workers_.length, doneEvents_[block]);
            }
        }

        if (workerAgentCount_ != getNumAgents()) {
            workerAgentCount_ = getNumAgents();
            for (int block = 0; block < workers_.length; ++block) {
                workers_[block].config(block * getNumAgents() / workers_.length, (block + 1) * getNumAgents() / workers_.length);
            }
        }

        kdTree_.buildAgentTree();

        for (int block = 0; block < workers_.length; ++block) {
            doneEvents_[block] = new CountDownLatch(1);
            workers_[block].doneEvent_ = doneEvents_[block];
            workers_[block].setStep(true);
            executor_.submit(workers_[block]);
            //workers_[block].run();
        }

        try {
            for (CountDownLatch event : doneEvents_) {
                event.await();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        for (int block = 0; block < workers_.length; ++block) {
            doneEvents_[block] = new CountDownLatch(1);
            workers_[block].doneEvent_ = doneEvents_[block];
            workers_[block].setStep(false);
            executor_.submit(workers_[block]);
            //workers_[block].run();
        }

        try {
            for (CountDownLatch event : doneEvents_) {
                event.await();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        globalTime_ += timeStep_;

        return globalTime_;
    }

    /**
     * Returns the specified agent neighbor of the specified agent.
     *
     * @param agentNo The number of the agent whose agent neighbor is to be retrieved.
     * @param neighborNo The number of the agent neighbor to be retrieved.
     * @return The number of the neighboring agent.
     */
    public int getAgentAgentNeighbor(int agentNo, int neighborNo) {
        return agents_.get(agentNo2indexDict_.get(agentNo)).agentNeighbors_.get(neighborNo).getValue().id_;
    }

    /**
     * Returns the maximum neighbor count of a specified agent.
     *
     * @param agentNo The number of the agent whose maximum neighbor count is to be retrieved.
     * @return The present maximum neighbor count of the agent.
     */
    public int getAgentMaxNeighbors(int agentNo) {
        return agents_.get(agentNo2indexDict_.get(agentNo)).maxNeighbors_;
    }

    /**
     * Returns the maximum speed of a specified agent.
     *
     * @param agentNo The number of the agent whose maximum speed is to be retrieved.
     * @return The present maximum speed of the agent.
     */
    public float getAgentMaxSpeed(int agentNo) {
        return agents_.get(agentNo2indexDict_.get(agentNo)).maxSpeed_;
    }

    /**
     * Returns the maximum neighbor distance of a specified agent.
     *
     * @param agentNo The number of the agent whose maximum neighbor distance is to be retrieved.
     * @return The present maximum neighbor distance of the agent.
     */
    public float getAgentNeighborDist(int agentNo) {
        return agents_.get(agentNo2indexDict_.get(agentNo)).neighborDist_;
    }

    /**
     * Returns the count of agent neighbors taken into account to compute the current velocity for the specified agent.
     *
     * @param agentNo The number of the agent whose count of agent neighbors is to be retrieved.
     * @return The count of agent neighbors taken into account to compute the current velocity for the specified agent.
     */
    public int getAgentNumAgentNeighbors(int agentNo) {
        return agents_.get(agentNo2indexDict_.get(agentNo)).agentNeighbors_.size();
    }

    /**
     * Returns the count of obstacle neighbors taken into account to compute the current velocity for the specified agent.
     *
     * @param agentNo The number of the agent whose count of obstacle neighbors is to be retrieved.
     * @return The count of obstacle neighbors taken into account to compute the current velocity for the specified agent.
     */
    public int getAgentNumObstacleNeighbors(int agentNo) {
        return agents_.get(agentNo2indexDict_.get(agentNo)).obstacleNeighbors_.size();
    }

    /**
     * Returns the specified obstacle neighbor of the specified agent.
     *
     * @param agentNo The number of the agent whose obstacle neighbor is to be retrieved.
     * @param neighborNo The number of the obstacle neighbor to be retrieved.
     * @return The number of the first vertex of the neighboring obstacle edge.
     */
    public int getAgentObstacleNeighbor(int agentNo, int neighborNo) {
        return agents_.get(agentNo2indexDict_.get(agentNo)).obstacleNeighbors_.get(neighborNo).getValue().id_;
    }

    /**
     * Returns the ORCA constraints of the specified agent.
     *
     * @param agentNo The number of the agent whose ORCA constraints are to be retrieved.
     * @return A list of lines representing the ORCA constraints.
     * @remarks The halfplane to the left of each line is the region of permissible velocities with respect to that ORCA constraint.
     */
    public List<Line> getAgentOrcaLines(int agentNo) {
        return agents_.get(agentNo2indexDict_.get(agentNo)).orcaLines_;
    }

    /**
     * Returns the two-dimensional position of a specified agent.
     *
     * @param agentNo The number of the agent whose two-dimensional position is to be retrieved.
     * @return The present two-dimensional position of the (center of the) agent.
     */
    public Vector2 getAgentPosition(int agentNo) {
        return agents_.get(agentNo2indexDict_.get(agentNo)).position_;
    }

    /**
     * Returns the two-dimensional preferred velocity of a specified agent.
     *
     * @param agentNo The number of the agent whose two-dimensional preferred velocity is to be retrieved.
     * @return The present two-dimensional preferred velocity of the agent.
     */
    public Vector2 getAgentPrefVelocity(int agentNo) {
        return agents_.get(agentNo2indexDict_.get(agentNo)).prefVelocity_;
    }

    /**
     * Returns the radius of a specified agent.
     *
     * @param agentNo The number of the agent whose radius is to be retrieved.
     * @return The present radius of the agent.
     */
    public float getAgentRadius(int agentNo) {
        return agents_.get(agentNo2indexDict_.get(agentNo)).radius_;
    }

    /**
     * Returns the time horizon of a specified agent.
     *
     * @param agentNo The number of the agent whose time horizon is to be retrieved.
     * @return The present time horizon of the agent.
     */
    public float getAgentTimeHorizon(int agentNo) {
        return agents_.get(agentNo2indexDict_.get(agentNo)).timeHorizon_;
    }

    /**
     * Returns the time horizon with respect to obstacles of a specified agent.
     *
     * @param agentNo The number of the agent whose time horizon with respect to obstacles is to be retrieved.
     * @return The present time horizon with respect to obstacles of the agent.
     */
    public float getAgentTimeHorizonObst(int agentNo) {
        return agents_.get(agentNo2indexDict_.get(agentNo)).timeHorizonObst_;
    }

    /**
     * Returns the two-dimensional linear velocity of a specified agent.
     *
     * @param agentNo The number of the agent whose two-dimensional linear velocity is to be retrieved.
     * @return The present two-dimensional linear velocity of the agent.
     */
    public Vector2 getAgentVelocity(int agentNo) {
        return agents_.get(agentNo2indexDict_.get(agentNo)).velocity_;
    }

    /**
     * Returns the global time of the simulation.
     *
     * @return The present global time of the simulation (zero initially).
     */
    public float getGlobalTime() {
        return globalTime_;
    }

    /**
     * Returns the count of agents in the simulation.
     *
     * @return The count of agents in the simulation.
     */
    public int getNumAgents() {
        return agents_.size();
    }

    /**
     * Returns the count of obstacle vertices in the simulation.
     *
     * @return The count of obstacle vertices in the simulation.
     */
    public int getNumObstacleVertices() {
        return obstacles_.size();
    }

    /**
     * Returns the count of workers.
     *
     * @return The count of workers.
     */
    public int GetNumWorkers() {
        return numWorkers_;
    }

    /**
     * Returns the two-dimensional position of a specified obstacle vertex.
     *
     * @param vertexNo The number of the obstacle vertex to be retrieved.
     * @return The two-dimensional position of the specified obstacle vertex.
     */
    public Vector2 getObstacleVertex(int vertexNo) {
        return obstacles_.get(vertexNo).point_;
    }

    /**
     * Returns the number of the obstacle vertex succeeding the specified obstacle vertex in its polygon.
     *
     * @param vertexNo The number of the obstacle vertex whose successor is to be retrieved.
     * @return The number of the obstacle vertex succeeding the specified obstacle vertex in its polygon.
     */
    public int getNextObstacleVertexNo(int vertexNo) {
        return obstacles_.get(vertexNo).next_.id_;
    }

    /**
     * Returns the number of the obstacle vertex preceding the specified obstacle vertex in its polygon.
     *
     * @param vertexNo The number of the obstacle vertex whose predecessor is to be retrieved.
     * @return The number of the obstacle vertex preceding the specified obstacle vertex in its polygon.
     */
    public int getPrevObstacleVertexNo(int vertexNo) {
        return obstacles_.get(vertexNo).previous_.id_;
    }

    /**
     * Returns the time step of the simulation.
     *
     * @return The present time step of the simulation.
     */
    public float getTimeStep() {
        return timeStep_;
    }

    /**
     * Processes the obstacles that have been added so that they are accounted for in the simulation.
     *
     * @remarks Obstacles added to the simulation after this function has been called are not accounted for in the simulation.
     */
    public void processObstacles() {
        kdTree_.buildObstacleTree();
    }

    /**
     * Performs a visibility query between the two specified points with respect to the obstacles.
     *
     * @param point1 The first point of the query.
     * @param point2 The second point of the query.
     * @param radius The minimal distance between the line connecting the two points and the obstacles in order for the points to be mutually visible (optional). Must be non-negative.
     * @return A boolean specifying whether the two points are mutually visible. Returns true when the obstacles have not been processed.
     */
    public boolean queryVisibility(Vector2 point1, Vector2 point2, float radius) {
        return kdTree_.queryVisibility(point1, point2, radius);
    }

    public int queryNearAgent(Vector2 point, float radius) {
        if (getNumAgents() == 0)
            return -1;
        return kdTree_.queryNearAgent(point, radius);
    }

    /**
     * Sets the default properties for any new agent that is added.
     *
     * @param neighborDist The default maximum distance (center point to center point) to other agents a new agent takes into account in the navigation. The larger this number, the longer he running time of the simulation. If the number is too low, the simulation will not be safe. Must be non-negative.
     * @param maxNeighbors The default maximum number of other agents a new agent takes into account in the navigation. The larger this number, the longer the running time of the simulation. If the number is too low, the simulation will not be safe.
     * @param timeHorizon The default minimal amount of time for which a new agent's velocities that are computed by the simulation are safe with respect to other agents. The larger this number, the sooner an agent will respond to the presence of other agents, but the less freedom the agent has in choosing its velocities. Must be positive.
     * @param timeHorizonObst The default minimal amount of time for which a new agent's velocities that are computed by the simulation are safe with respect to obstacles. The larger this number, the sooner an agent will respond to the presence of obstacles, but the less freedom the agent has in choosing its velocities. Must be positive.
     * @param radius The default radius of a new agent. Must be non-negative.
     * @param maxSpeed The default maximum speed of a new agent. Must be non-negative.
     * @param velocity The default initial two-dimensional linear velocity of a new agent.
     */
    public void setAgentDefaults(float neighborDist, int maxNeighbors, float timeHorizon, float timeHorizonObst, float radius, float maxSpeed, Vector2 velocity) {
        if (defaultAgent_ == null) {
            defaultAgent_ = new Agent();
        }

        defaultAgent_.maxNeighbors_ = maxNeighbors;
        defaultAgent_.maxSpeed_ = maxSpeed;
        defaultAgent_.neighborDist_ = neighborDist;
        defaultAgent_.radius_ = radius;
        defaultAgent_.timeHorizon_ = timeHorizon;
        defaultAgent_.timeHorizonObst_ = timeHorizonObst;
        defaultAgent_.velocity_ = velocity;
    }

    /**
     * Sets the maximum neighbor count of a specified agent.
     *
     * @param agentNo The number of the agent whose maximum neighbor count is to be modified.
     * @param maxNeighbors The replacement maximum neighbor count.
     */
    public void setAgentMaxNeighbors(int agentNo, int maxNeighbors) {
        agents_.get(agentNo2indexDict_.get(agentNo)).maxNeighbors_ = maxNeighbors;
    }

    /**
     * Sets the maximum speed of a specified agent.
     *
     * @param agentNo The number of the agent whose maximum speed is to be modified.
     * @param maxSpeed The replacement maximum speed. Must be non-negative.
     */
    public void setAgentMaxSpeed(int agentNo, float maxSpeed) {
        agents_.get(agentNo2indexDict_.get(agentNo)).maxSpeed_ = maxSpeed;
    }

    /**
     * Sets the maximum neighbor distance of a specified agent.
     *
     * @param agentNo The number of the agent whose maximum neighbor distance is to be modified.
     * @param neighborDist The replacement maximum neighbor distance. Must be non-negative.
     */
    public void setAgentNeighborDist(int agentNo, float neighborDist) {
        agents_.get(agentNo2indexDict_.get(agentNo)).neighborDist_ = neighborDist;
    }

    /**
     * Sets the two-dimensional position of a specified agent.
     *
     * @param agentNo The number of the agent whose two-dimensional position is to be modified.
     * @param position The replacement of the two-dimensional position.
     */
    public void setAgentPosition(int agentNo, Vector2 position) {
        agents_.get(agentNo2indexDict_.get(agentNo)).position_ = position;
    }

    /**
     * Sets the two-dimensional preferred velocity of a specified agent.
     *
     * @param agentNo The number of the agent whose two-dimensional preferred velocity is to be modified.
     * @param prefVelocity The replacement of the two-dimensional preferred velocity.
     */
    public void setAgentPrefVelocity(int agentNo, Vector2 prefVelocity) {
        agents_.get(agentNo2indexDict_.get(agentNo)).prefVelocity_ = prefVelocity;
    }

    /**
     * Sets the radius of a specified agent.
     *
     * @param agentNo The number of the agent whose radius is to be modified.
     * @param radius The replacement radius. Must be non-negative.
     */
    public void setAgentRadius(int agentNo, float radius) {
        agents_.get(agentNo2indexDict_.get(agentNo)).radius_ = radius;
    }

    /**
     * Sets the time horizon of a specified agent with respect to other agents.
     *
     * @param agentNo The number of the agent whose time horizon is to be modified.
     * @param timeHorizon The replacement time horizon with respect to other agents. Must be positive.
     */
    public void setAgentTimeHorizon(int agentNo, float timeHorizon) {
        agents_.get(agentNo2indexDict_.get(agentNo)).timeHorizon_ = timeHorizon;
    }

    /**
     * Sets the time horizon of a specified agent with respect to obstacles.
     *
     * @param agentNo The number of the agent whose time horizon with respect to obstacles is to be modified.
     * @param timeHorizonObst The replacement time horizon with respect to obstacles. Must be positive.
     */
    public void setAgentTimeHorizonObst(int agentNo, float timeHorizonObst) {
        agents_.get(agentNo2indexDict_.get(agentNo)).timeHorizonObst_ = timeHorizonObst;
    }

    /**
     * Sets the two-dimensional linear velocity of a specified agent.
     *
     * @param agentNo The number of the agent whose two-dimensional linear velocity is to be modified.
     * @param velocity The replacement two-dimensional linear velocity.
     */
    public void setAgentVelocity(int agentNo, Vector2 velocity) {
        agents_.get(agentNo2indexDict_.get(agentNo)).velocity_ = velocity;
    }

    /**
     * Sets the global time of the simulation.
     *
     * @param globalTime The global time of the simulation.
     */
    public void setGlobalTime(float globalTime) {
        globalTime_ = globalTime;
    }

    /**
     * Sets the number of workers.
     *
     * @param numWorkers The number of workers.
     */
    public void SetNumWorkers(int numWorkers) {
        numWorkers_ = numWorkers;

        if (numWorkers_ <= 0) {
            numWorkers_ = Runtime.getRuntime().availableProcessors();
        }
        workers_ = null;
        workerAgentCount_ = 0;
        if (executor_ != null) {
            executor_.shutdown();
            executor_ = null;
        }
    }

    /**
     * Sets the time step of the simulation.
     *
     * @param timeStep The time step of the simulation. Must be positive.
     */
    public void setTimeStep(float timeStep) {
        timeStep_ = timeStep;
    }

    /**
     * Constructs and initializes a simulation.
     */
    public Simulator() {
        Clear();
    }
}
